#!/usr/bin/env python
#-*- coding: utf-8 -*-
#
# Динамическая карта. Используется пакет Tkinter для библиотек Tcl/Tk x.x.
# Еремин Г.А., ЦНИИ Комета, 2011.
# egax@bk.ru

"""
Основные сочетания клавиш:
<левая кнопка мыши>                    центрирование карты по курсору
<Control> + <левая кнопка мыши>        рисование фигуры с подсчетом длин сторон
<Shift> + <левая кнопка мыши>          движение/перемещение карты по курсору
<правая кнопка мыши>                   контекстное меню
<влево-вправо-вверх-вниз>              прокрутка карты
<Shift> + <влево-вправо-вверх-вниз>    поворот сферы (для проекции Сфера)
<+>|<->|<Space>|<BackSpace>            изменение масштаба
"""

__version__ =  "2.2.3"

import math, re

#from Tkinter import *
from tkColorChooser import *
from tkSimpleDialog import *
from tkMessageBox import *

from tablelist import *

class dbCarta:
    """Класс формы для создания и управления картой."""

    def __init__(self, master, **kw):
        """Конструктор класса. Инициализируем переменные и создаем виджеты.
        MASTER указатель на форму.
        **KW `dictionary` c параметрами командной строки на входе скрипта."""
        # шаг для градуса в секундах
        # размеры карты по горизонтали (x) (для полушария (по 180)) в секундах
        self.__delta = 3600.0
        self.__halfX = 648000.0
        # ограничение для широты для `меркатор`
        self.__ylimit = 84
        # `dq` классы и типы контуров и их параметры
        self.__mopt = {
            '_Arctic':      {'class': 'Polygon', 'fg': '#%02x%02x%02x' % (210, 221, 195), 'bg': '#%02x%02x%02x' % (210, 221, 195)},
            '_Mainland':    {'class': 'Polygon', 'fg': '#%02x%02x%02x' % (135, 159, 103), 'bg': '#%02x%02x%02x' % (135, 159, 103)},
            '_Water':       {'class': 'Polygon', 'fg': '#%02x%02x%02x' % (186, 196, 205), 'bg': '#%02x%02x%02x' % (186, 196, 205)},
            '_WaterLine':   {'class': 'Line', 'fg': '#%02x%02x%02x' % (186, 196, 205), 'smooth': 1},
            '_Latitude':    {'class': 'Line', 'fg': '#%02x%02x%02x' % (164, 164, 164), 'anchor': 'sw'},
            '_Longtitude':  {'class': 'Line', 'fg': '#%02x%02x%02x' % (164, 164, 164), 'anchor': 'nw'},
            'DotPort':      {'class': 'Dot', 'fg': '#%02x%02x%02x' % (34, 104, 234)},
            'DotRoute':     {'class': 'Dot', 'fg': '#%02x%02x%02x' % (34, 104, 234)},
            'Area':         {'class': 'Polygon', 'fg': '#%02x%02x%02x' % (0, 130, 200), 'bg': ''},
            'SCT':          {'class': 'Polygon', 'fg': '#%02x%02x%02x' % (100, 110, 100)},
            'Line':         {'class': 'Line', 'fg': '#%02x%02x%02x' % (0, 130, 200)},
            'Route':        {'class': 'Line', 'fg': '#%02x%02x%02x' % (0, 130, 200)},
            'Figure':       {'class': 'Line', 'fg': '#%02x%02x%02x' % (0, 130, 200), 'width': 2},
            'CurrFigure':   {'class': 'Line', 'fg': '#%02x%02x%02x' % (0, 130, 200), 'anchor': 'ne', 'width': 2},
            'UserLine':     {'class': 'Line', 'fg': '#%02x%02x%02x' % (0, 0, 0)} }
        # `dq` для временных значений (между вызовами событий и функций)
        self.__temp = {}
        # `dq` значений объектов на карте (коорд., надпись, значок)
        self.__mflood = {}
        # язык (локаль по умолчанию)
        if (not setLanguage()): setLanguage('en')
        # настройки окна
        master.protocol('WM_DELETE_WINDOW', master.quit)
        master.title('dbCarta')
        parent = Frame(master)
        parent.pack(fill='both', expand=1)
        parent.columnconfigure(0, weight=1)
        parent.rowconfigure(1, weight=1)
        # указатель на окно и контейнер
        self.master = master
        self.parent = parent
        # создаем виджеты и грузим карту
        self.__createCtls()
        self.changeProject(kw.get('project', 0))
        self.__createMeridians()
        master.update()

    def __createMenu(self, dynmenu=()):
        """Создаем меню.
        DYNMENU (оп.) список списков аргументов для динамически добавляемых элементов меню
           (`функция для command`, `название меню`, `аргумент функции`, ...)."""
        # реализуем меню в виде `dq`
        self.menu = {}
        self.menu['menu'] = Menu(self.parent, tearoff=0)
        self.menu['menu.project'] = Menu(self.menu['menu'], tearoff=0)
        self.menu['menu.project'].add_radiobutton(label=_(u'Меркатор'), command=lambda: self.changeProject(0))
        self.menu['menu.project'].add_radiobutton(label=_(u'Линейная'), command=lambda: self.changeProject(1))
        self.menu['menu.project'].add_radiobutton(label=_(u'Сфера'), command=lambda: self.changeProject(2))
        self.menu['menu'].add_cascade(label=_(u'Проекция'), menu=self.menu['menu.project'])
        self.menu['menu'].add_separator()
        self.menu['menu'].add_command(label=_(u'Цвет: Линии объектов...'), command=lambda: self.colorCarta('fg', 0, 'Area', 'Route', 'Line'))
        self.menu['menu.fillmenu'] = Menu(self.menu['menu'], tearoff=0)
        self.menu['menu.fillmenu'].add_command(label=_(u'Установить прозрачный цвет'), command=lambda: self.colorCarta('bg', 1, 'Area'))
        self.menu['menu.fillmenu'].add_command(label=_(u'Цвет заливки...'), command=lambda: self.colorCarta('bg', 0, 'Area'))
        self.menu['menu'].add_cascade(label=_(u'Заливка: Контуры объектов'), menu=self.menu['menu.fillmenu'])
        self.menu['menu'].add_separator()
        self.menu['menu.clrmenu'] = Menu(self.menu['menu'], tearoff=0)
        self.menu['menu.clrmenu'].add_command(label=_(u'Отображение подложки'), command=lambda: self.clearLayers('_Mainland', '_Water', '_Arctic', '_WaterLine'))
        self.menu['menu.clrmenu'].add_command(label=_(u'Отображения контуров'), command=lambda: self.clearLayers('Area', 'Line'))
        self.menu['menu.clrmenu'].add_command(label=_(u'Отображения трасс'), command=lambda: self.clearLayers('DotRoute', 'Route'))
        self.menu['menu.clrmenu'].add_command(label=_(u'Отображения портов'), command=lambda: self.clearLayers('DotPort'))
        # self.menu['menu'].add_command(label=_(u'Очистить: Отображения шаблонов'), command=lambda: self.clearLayers('UserLine'))
        self.menu['menu.clrmenu'].add_command(label=_(u'Отображения фигур'), command=lambda: (self.__temp.pop('Figure', ''), self.clearLayers('Figure')))
        self.menu['menu'].add_cascade(label=_(u'Очистить'), menu=self.menu['menu.clrmenu'])
        self.menu['menu'].add_separator()
        self.menu['menu.langmenu'] = Menu(self.menu['menu'], tearoff=0)
        self.menu['menu.langmenu'].add_radiobutton(label=_(u'Русский'), command=lambda: setLanguage('ru'))
        self.menu['menu.langmenu'].add_radiobutton(label=_(u'Английский'), command=lambda: setLanguage('en'))
        self.menu['menu'].add_cascade(label=_(u'Язык'), menu=self.menu['menu.langmenu'])
        self.menu['menu'].add_command(label=_(u'Справка'), command=lambda: showinfo(title='dbCarta', message=_(__doc__.decode('utf8')).strip('\n').replace('\n', '\n\n')))
        # добавляем динамические элементы меню из списка dynmenu
        k = {}
        for i, x in enumerate(dynmenu):
            if not i:
                addmenu = lambda x: self.menu[x[0]].add_command(label=x[1], command=lambda: x[2](*x[3:]))
            if not k.has_key(x[0]):
                self.menu[x[0]].add_separator()
                k[x[0]] = ''
            addmenu(x)

    def __createCtls(self):
        """Создаем виджеты на форме."""
        #self.parent.bind('<Configure>', lambda ev: self.labelPoint())
        self.master.bind('<Key>', self.__master_keyPress)
        self.master.bind('<Shift-Key>', self.__master_keyPressShift)
        # виджет Scale для масштабирования
        self.slider = Scale(self.parent, label=_(u'Координаты'), orient='horizontal',
                            from_=0.0005, to=1.0, resolution=0.0005, command=self.scaleCarta)
        self.slider.var = DoubleVar()
        self.slider.config(variable=self.slider.var)
        self.slider.grid(column=0, row=0, columnspan=2, sticky='ew')
        # виджет Canvas с прокрутками
        self.dw = Canvas(self.parent, bd=0, bg=self.__mopt['_Water']['fg'])
        self.scrollX = Scrollbar(self.parent, orient='horizontal',
                                 command=lambda *ev: self.dw.xview(*ev))
        self.scrollY = Scrollbar(self.parent, orient='vertical',
                                 command=lambda *ev: self.dw.yview(*ev))
        self.scrollX.bind('<ButtonRelease-1>', lambda ev: self.labelPoint())
        self.scrollY.bind('<ButtonRelease-1>', lambda ev: self.labelPoint())
        self.scrollX.grid(column=0, row=2, sticky='ew')
        self.scrollY.grid(column=1, row=1, sticky='ns')
        self.dw.config(xscrollcommand=self.scrollX.set, yscrollcommand=self.scrollY.set)
        self.dw.grid(column=0, row=1, sticky='nsew')
        self.dw.bind('<Button>', self.__dw_mouseDown)
        self.dw.bind('<Control-Button-1>', self.__dw_mouseDownCtrl)
        self.dw.bind('<Shift-Button-1>', self.__dw_mouseDownShift)
        self.dw.bind('<ButtonRelease-1>', self.__dw_mouseUp)
        self.dw.bind('<Control-ButtonRelease-1>', self.__dw_mouseUpCtrl)
        self.dw.bind('<Shift-ButtonRelease-1>', self.__dw_mouseUpShift)
        self.dw.bind('<Motion>', self.__dw_mouseMove)
        self.dw.bind('<Shift-Motion>', self.__dw_mouseMoveShift)

    def __createMeridians(self):
        """Создаем линии координатной сетки."""
        # X
        for x in range(-180, 541, 30):
            lon = []
            for y in range(-90, 91, 90):
                lon += [[x, y]]
            self.loadCarta([('_Longtitude', str([x, y]), str(lon), str(x), str(lon[0]))])
        # Y
        for y in range(-90, 91, 30):
            lat = [[-180, y]]
            label_y = str(y)
            center_y = str(lat[0])
            for x in range(-150, 541, 30):
                lat += [[x, y]]                
                self.loadCarta([('_Latitude', str([x, y]), str(lat), label_y, center_y)])
                label_y = None
                center_y = None
                lat.pop(0)

    def __listCoords(self, title, ftag, x, y):
        """Создаем окно с tablelist для вывода координат объекта.
        TITLE название объекта из __mflood.
        FTAG таг контура из __mflood.
        X координата X левого верхнего угла окна в пикселях по горизонтали.
        Y координата Y левого верхнего угла окна в пикселях по вертикали."""
        # создаем окно если оно не создано для объекта с ftag
        if not self.__temp.has_key('coords.%s' % ftag):
            _self = Toplevel(self.parent)
            _self.title(title)
            _self.geometry('220x200+%s+%s' % (x, y))
            _self.bind('<Destroy>', lambda ev: self.__temp.pop('coords.%s' % ftag, ''))
            _self.tlist = TableList(_self, activestyle = 'none', columns = (0, '#', 0, _(u'Долгота'), 0, _(u'Широта')),
                                    setfocus=1, selectmode='extended', selecttype='row', stretch = 'all')
            _self.scrollY = Scrollbar(_self, orient='vertical', command=_self.tlist.yview)
            _self.scrollY.pack(fill='y', side='right')
            _self.tlist.config(yscrollcommand=_self.scrollY.set)
            _self.tlist.pack(fill='both', expand=1)
            self.__temp['coords.%s' % ftag] = ''
            # читаем координаты контура из __mflood в градусах и заполняем список
            coords = self.__mflood[ftag]['coords']
            for i, _coords in enumerate(coords):
                _self.tlist.insert('end', tuple([i] + _coords))

    def __master_keyPress(self, ev):
        """Обработчик события `нажатая клавиша` для формы.
        EV объект события с параметрами нажатой клавиши."""
        # последовательно увеличиваем/уменьшаем масштаб
        if ev.keysym in ('KP_Add', 'plus', 'space', 'KP_Subtract', 'minus', 'BackSpace'):
            self.slider.var.set(self.slider.get() + self.slider['resolution'] * (1, -1)[ev.keysym in ('KP_Subtract', 'minus', 'BackSpace')])
            self.scaleCarta()
        # двигаем карту влево-вправо-вверх-вниз
        elif ev.keysym in ('Left', 'Right'):
            self.dw.xview('scroll', (1, -1)[ev.keysym == 'Left'], 'units')
            self.labelPoint()
        elif ev.keysym in ('Up', 'Down'):
            self.dw.yview('scroll', (1, -1)[ev.keysym == 'Up'], 'units')
            self.labelPoint()

    def __master_keyPressShift(self, ev):
        """Обработчик события `Shift + нажатая клавиша` для формы.
        EV объект события с параметрами нажатой клавиши."""
        if self.__project == 2:
            if ev.keysym == 'Left':
                self.__temp['centerof'][0][0] -= 10
            elif ev.keysym == 'Right':
                self.__temp['centerof'][0][0] += 10
            if ev.keysym == 'Up':
                self.__temp['centerof'][0][1] -= 10
            elif ev.keysym == 'Down':
                self.__temp['centerof'][0][1] += 10
            # приведение в диапазон по x(-180..180), y(-180..180)
            self.__temp['centerof'] = [[
                math.degrees(P(math.radians(self.__temp['centerof'][0][0]), math.pi)),
                math.degrees(P(math.radians(self.__temp['centerof'][0][1]), math.pi))]]
            self.changeProject(self.__project)
        else:
            self.__master_keyPress(ev)

    def __dw_mouseDown(self, ev):
        """Обработчик события `нажатый клик` для карты.
        EV объект события с параметрами нажатой кнопки."""
        x, y = self.dw.canvasx(ev.x), self.dw.canvasy(ev.y)
        if ev.num == 3:
            # добавляем меню
            mnu = [('menu', _(u'Нарисовать фигуру...'), lambda : self.loadCarta( [('Figure', self.freeTag('Figure'), askstring(_(u'Нарисовать фигуру'), _(u'Введите координаты %s') % ("'(x,y),(x1,y1),...'",)))] ))]
            # добавляем меню для объектов под курсором (не надписей)
            for pid in self.dw.find_overlapping(x - 5, y - 5, x + 5, y + 5):
                ftag = self.dw.gettags(pid)[0]
                if ftag[:1] != '_':
                    label = self.__mflood[ftag]['label']
                    if not label:
                        label = ftag
                    mnu.append( ('menu', _(u'Координаты %s') % (label,), self.__listCoords, _(u'Координаты %s') % (label,), ftag, ev.x_root, ev.y_root) )
                    mnu.append( ('menu.clrmenu', _(u'Отображение %s') % (label,), self.clearCarta, ftag) )
            # создаем элементы меню
            self.__createMenu(mnu)
            self.menu['menu'].tk_popup(ev.x_root, ev.y_root)
        elif self.__temp.has_key('Figure'):
            # дорисовываем фигуру и удаляем `остатки`
            self.__dw_mouseDownCtrl(ev)
            self.__temp.pop('Figure', '')
            self.clearCarta('CurrFigure')
        elif ev.num == 1:
            # центрируем по клику
            self.centerPoint(x, y)

    def __dw_mouseDownCtrl(self, ev):
        """Обработчик события `Control + нажатый клик` для карты.
        EV объект события с параметрами нажатой кнопки."""
        x, y = self.dw.canvasx(ev.x), self.dw.canvasy(ev.y)
        # создаем или продолжаем контур фигуры
        if self.__temp.has_key('Figure'):
            self.__temp['Figure'][1] += self.__temp['Figure'][0]
            self.__temp['Figure'][2] = self.fromPoints([x, y])
            self.paintCarta(self.__temp['Figure'][2], 'Figure', 'Figure.' + self.__temp['Figure'][3], addcoords=1)
        else:
            # запоминаем нач. значения расстояний, координаты и ключ контура
            self.__temp['Figure'] = [0, 0, self.fromPoints([x, y]), str(len(self.dw.find_withtag('Figure')) + 1)]
            self.loadCarta( [('Figure', self.__temp['Figure'][3], str(self.__temp['Figure'][2]))] )

    def __dw_mouseDownShift(self, ev):
        """Обработчик события `Shift + нажатый клик` для холста.
        EV объект события с параметрами нажатой кнопки."""
        if ev.num == 1:
            # сохраняем текущие координаты и центр видимого окна в пикселях
            self.__temp['shift'] = [ ev,
                [self.dw.winfo_width() / 2.0,
                 self.dw.winfo_height() / 2.0] ]

    def __dw_mouseMove(self, ev):
        """Обработчик события `движение мышью` для холста.
        EV объект события с параметрами нажатой кнопки."""
        x, y = self.dw.canvasx(ev.x), self.dw.canvasy(ev.y)
        # двигаем линию расстояний
        if self.__temp.has_key('Figure'):
            coords = self.__temp['Figure'][2] + self.fromPoints([x, y])
            self.__temp['Figure'][0] = self.distance(coords)
            self.paintCarta(coords, 'CurrFigure', 'CurrFigure.1')
            self.paintCarta([coords[1]], 'CurrFigure', '_CurrFigure.1', _(u'%s\n%s миль') % tuple(self.__temp['Figure'][:2]))
        # считаем и выводим координаты в градусах по курсору мыши
        coords = map(lambda x: str(x), self.fromPoints((x, y))[0])
        self.slider.config(label=_(u'Ш  %s  Д  %s') % (coords[1], coords[0]))

    def __dw_mouseMoveShift(self, ev):
        """Обработчик события `Shift + движение мышью` для холста.
        EV объект события с параметрами нажатой кнопки."""
        if self.__temp.has_key('shift'):
            # центрируем по движению мыши и сохраняем новые текущие координаты
            self.centerPoint(
                self.dw.canvasx(self.__temp['shift'][1][0] - (ev.x - self.__temp['shift'][0].x)),
                self.dw.canvasy(self.__temp['shift'][1][1] - (ev.y - self.__temp['shift'][0].y)) )
            self.__temp['shift'][0] = ev

    def __dw_mouseUp(self, ev):
        """Обработчик события `отжатый клик` для холста.
        EV объект события с параметрами нажатой кнопки."""
        self.labelPoint()

    def __dw_mouseUpCtrl(self, ev):
        """Обработчик события `Control + отжатый клик` для холста.
        EV объект события с параметрами нажатой кнопки."""
        pass

    def __dw_mouseUpShift(self, ev):
        """Обработчик события `Shift + отжатый клик` для холста.
        EV объект события с параметрами нажатой кнопки."""
        self.__temp.pop('shift', '')
        self.labelPoint()

    #-----------------------------------------------------

    def centerPoint(self, x=None, y=None):
        """Центрируем точку на карте.
        X (оп.) `пойнты` по горизонтали.
        Y (оп.) `пойнты` по вертикали."""
        # читаем текущие положения видимого окна холста
        scrollX = self.dw.xview()
        scrollY = self.dw.yview()
        # проверяем параметры
        if x and y:
            # центрируем точку здесь
            moveX = x / (self.__scaleX * self.slider.var.get()) - (scrollX[1] - scrollX[0]) / 2.0
            moveY = y / (self.__scaleY * self.slider.var.get()) - (scrollY[1] - scrollY[0]) / 2.0
        else:
            # иначе считаем видимый центр
            moveX = self.__temp['center_x'] - (scrollX[1] - scrollX[0]) / 2.0
            moveY = self.__temp['center_y'] - (scrollY[1] - scrollY[0]) / 2.0
        # прокручиваем dw
        self.dw.xview('moveto', moveX)
        self.dw.yview('moveto', moveY)

    def changeProject(self, new_project=0):
        """Меняем проекцию карты. Пересчитываем координаты отображенных контуров и надписей.
        Обработчик события `клик` меню `Настройки` > `Проекция: ...`.
        NEW_PROJECT (оп.) индекс новой проекции {
            0 меркатор (по умолчанию) |
            1 линейная |
            2 сфера}."""
        # меняем проекцию карты
        centerof = []
        if new_project == 0:     # меркатор
            self.__scaleX = self.__halfX * 4.0
            self.__scaleY = 2 * self.toMercator(90.0) * self.__delta
        elif new_project == 1:   # линейная
            self.__scaleX = self.__halfX * 4.0
            self.__scaleY = self.__halfX
        elif new_project == 2:   # сфера
            self.__scaleX = self.__halfX * 2.0
            self.__scaleY = self.__halfX
            # центр проекции
            #centerof = self.toCoords(askstring("Центр сферы", "Введите центр проекции (x,y)"))
            if not centerof:
                centerof = self.__temp.get('centerof', [[0, 0]])
            self.__temp['centerof'] = centerof
        self.__halfY = self.__scaleY / 2.0
        # индекс текущей проекции
        self.__project = new_project
        self.scaleCarta(docenter=0)
        # перерисовываем контура из __mflood в новой проекции
        for ftag, value in self.__mflood.iteritems():
            self.paintCarta(value['coords'], ftag.split('.')[0], ftag)
        self.labelPoint()

    def freeTag(self, ftype, i=1):
        """Возвращает "свободный" таг для слоя.
        FTYPE тип контура из __mopt.
        I (оп.) начальный индекс для поиска (1 по умолчанию)."""
        ftag = str(len(self.dw.find_withtag(ftype)) + i)
        if self.dw.find_withtag('%s.%s' % (ftype, ftag)):
            freeTag(i + 1, ftype)
        return ftag

    def labelPoint(self):
        """Выводит надписи в средних точках объектов в видимой области карты.
        Также является обработчиком команды-события <...Release> для виджетов и формы."""
        # координаты границ видимиго окна в `пойнтах`
        left, right = [self.__scaleX * self.slider.var.get() * x for x in self.dw.xview()]
        top, bottom = [self.__scaleY * self.slider.var.get() * y for y in self.dw.yview()]
        # координаты границ видимиго окна и центра (для проекции `Сфера`)  в градусах
        left, top = self.fromPoints([left, top])[0]
        right, bottom = self.fromPoints([right, bottom])[0]
        # удаляем все надписи и рисуем их в видимой области
        for ftag, value in self.__mflood.iteritems():
            if (not value['centerof' ] or not value['label']):
                continue
            # таг надписи & точка центра в градусах
            ftype  = ftag.split('.')[0]
            _ftag = '_' + ftag             # таг надписи
            __ftag = '_' + _ftag           # таг картинки
            center_x, center_y = value['centerof'][0]
            label = value['label']
            icon = value['icon']
            # ограничение широты для `меркатор`
            if self.__project == 0:
                if abs(center_y) > self.__ylimit:
                    center_y = self.__ylimit * [-1, 1][center_y > 0]
            # рисуем надписи объектов и координатную сетку
            self.dw.delete(_ftag)
            self.dw.delete(__ftag)
            if ftype in ('_Longtitude'):
                if self.__project == 2:
                    if -180 < center_x <= 180:
                        self.paintCarta([[center_x, 0]], ftype, _ftag, ftext=label)
                elif left <= center_x <= right:
                    self.paintCarta([[center_x, top]], ftype, _ftag, ftext=label)
            elif ftype in ('_Latitude'):
                if self.__project == 2:
                    self.paintCarta([[0, center_y]], ftype, _ftag, ftext=label)
                elif bottom <= center_y <= top:
                    # ограничение широты для `меркатор`
                    if self.__project == 0:
                        label = str(center_y).replace('.0', '')
                    self.paintCarta([[left, center_y]], ftype, _ftag, ftext=label)
            else:
                if self.__project == 2:
                    intro = 1
                else:
                    intro = (left <= center_x <= right and bottom <= center_y <= top)
                if intro:
                    # масштабируем смещение надписи (3 градуса по умолчанию)
                    _d = self.slider['from'] / self.slider.var.get() ; d = 3 * _d
                    # рисуем иконку если есть & текст
                    if icon:
                        self.paintCarta([[center_x + d, center_y]], ftype, __ftag, fimage=icon) ; d = icon.width() * _d
                    self.paintCarta([[center_x + d, center_y]], ftype, _ftag, ftext=label)

    def centerCarta(self, centerof='0,0'):
        """Центрируем карту по точке.
        CENTEROF координаты в граудусах в строке вида "(x0,y0)"."""
        _centerof = self.toCoords(centerof)
        points = self.toPoints(_centerof, doscale=1)
        if points:
            self.centerPoint(*points)
            self.labelPoint()

    def loadCarta(self, data=(), docenter=0):
        """Выводим объекты на карту. Сохраняем параметры объекта в __mflood. Рисуем через paintCarta.
        DATA (оп.) список из списков данных по объектам вида (
            0 тип контура из __mopt,
            1 таг контура (уникальный для объектов одного типа),
            2 координаты контура в градусах в строке вида "((x1,y1),...,(xn,yn))",
            3 название контура (для надписи),
            4 центр контура для привязки надписи в градусах в строке вида {'' (без надписи)|"(x,y)"},
            5 иконка (GIF) ).
        DOCENTER (оп.) центрировать при выводе {1|0 (по умолчанию)}."""
        for row in data:
            try:
                _row = dict([[i, x] for i, x in enumerate(row)])
                if not _row.get(2):
                    continue
                ftype, tag = _row[0], _row[1]
                ftag = '%s.%s' % (ftype, tag)
                coords = self.toCoords(_row[2])
                # сохраняем название, координаты центра и контура в __mflood
                centerof = []
                if _row.get(4):
                    centerof = self.toCoords(_row[4])
                self.__mflood[ftag] = {
                    'coords': coords,
                    'label': _row.get(3),
                    'centerof': centerof,
                    'icon': _row.get(5),
                    'fg': self.__mopt[ftype]['fg'],
                    'bg': self.__mopt[ftype].get('bg', '')}
                # рисуем объект на карте
                self.paintCarta(coords, ftype, ftag)
            except:
                print 'loadCarta: ', sys.exc_info()[0], sys.exc_info()[1]
                raise
        if data and docenter:
            if centerof:
                # центр для сферы
                if self.__project == 2:                
                    self.__temp['centerof'] = centerof
                    self.changeProject(self.__project)
                else:
                    self.centerCarta(str(centerof))

    def paintCarta(self, coords, ftype, ftag, ftext='', fimage=None, addcoords=0):
        """Рисуем контура, надписи, картинки на холсте.
        COORDS список координат [[x,y],[x1,y1]...] в градусах контура.
        FTYPE тип контура из __mopt.
        FTAG уникальный для FTYPE таг контура.
        FTEXT (оп.) название для надписи.
        FIMAGE (оп.) картинка GIF.
        ADDCOORDS (оп.) рисовать заново или добавить точки к контуру {1|0 (по умолчанию)}."""
        # для сферы аппроксимируем точки
        _coords = []
        if self.__project == 2:
            for i in range(len(coords) - 1):
                _coords += self.approxPoints(coords[i:i + 2])
        if not _coords:
            _coords = coords

        points = self.toPoints(_coords, doscale=1)
        if not addcoords:
            self.dw.delete(ftag)
        if not points:
            return

        # цвета контура или слоя
        fg = self.__mopt[ftype]['fg']
        bg = self.__mopt[ftype].get('bg', '')
        mflood = self.__mflood.get(ftag, 0)
        if (mflood):
            fg = mflood.get('fg', fg)
            bg = mflood.get('bg', bg)
    
        # добавляем точки/создаем контур с тагами ftag (уникальный таг контура), ftype (таг слоя)
        if addcoords:
            self.dw.coords(ftag, tuple(self.dw.coords(ftag) + points))
            self.__mflood[ftag]['coords'] += coords
        elif ftext:                                                 # надпись
            self.dw.create_text(points, anchor=self.__mopt[ftype].get('anchor', 'w'), text=ftext, tags=(ftag, ftype))
        elif fimage:                                                # картинка
            self.dw.create_image(points, anchor=self.__mopt[ftype].get('anchor', 'w'), image=fimage, tags=(ftag, ftype))
        elif self.__mopt[ftype]['class'] in ('Line'):               # линии
            if len(points) < 4:
                points = points * 2
            self.dw.create_line(points, fill=fg,
                                smooth=self.__mopt[ftype].get('smooth'), width=self.__mopt[ftype].get('width', 1), tags=(ftag, ftype))
        elif self.__mopt[ftype]['class'] in ('Polygon', 'Dot'):     # площадные/точечные
            if len(points) < 4:
                points = points * 2
            self.dw.create_polygon(points, fill=bg, outline=fg, tags=(ftag, ftype))

    def clearLayers(self, *ftypes):
        """Удаляем все контура слоев из __mopt.
        *FTYPES слои из __mopt."""
        ftags = []
        for ftag in self.__mflood.iterkeys():
            if ftag.split('.')[0] in ftypes:
                ftags.append(ftag)
        self.clearCarta(*ftags)

    def clearCarta(self, *ftags):
        """Удаляем контуры, надписи, иконки с холста, из __mflood.
        *FTAGS таги контуров."""
        for ftag in ftags:
            self.dw.delete(ftag)
            self.dw.delete('_' + ftag)
            self.dw.delete('__' + ftag)
            self.__mflood.pop(ftag, '')

    def colorCarta(self, option='fg', dotransparent=0, *ftypes):
        """Выбираем и сохраняем цвет или прозрачность для слоев.
        OPTION (оп.) таг параметра для изменения цвета из __mopt (по умолчанию 'fg').
        DOTRANSPARENT (оп.) задать прозрачный цвет {1|0 (по умолчанию)}.
        *FTYPES (оп.) список слоев из __mopt."""
        color = ''
        if not dotransparent:
            try:
                if self.__mopt[ftypes[0]].get(option):
                    color = askcolor(parent=self.parent, initialcolor=self.__mopt[ftypes[0]].get(option))
                else:
                    color = askcolor(parent=self.parent)
            except:
                return
            color = color[1]
        # сохраняем цвет в __mopt
        for ftype in ftypes:
            self.__mopt[ftype][option] = color

    def scaleCarta(self, docenter=1):
        """Изменяем масштаб холста с учетом предыдущего масштаба.
        Также является обработчиком команды-события для слайдера изменения масштаба.
        DOCENTER (оп.) центрировать при масштабировании {1 (по умолчанию)|0}."""
        # запоминаем видимый центр для centerPoint
        self.__temp['center_x'] = (self.dw.xview()[0] + self.dw.xview()[1]) / 2.0
        self.__temp['center_y'] = (self.dw.yview()[0] + self.dw.yview()[1]) / 2.0
        # считаем соотношение текущего и предыдущего (до изменения) масштабов
        ratio = self.slider.var.get() / self.__temp.get('scale', self.slider['resolution'])
        # масштабируем и центрируем если нужно c поправкой на ratio
        # выводим надписи с новым масштабом если нужно
        self.dw['scrollregion'] = (0, 0, self.__scaleX * self.slider.var.get(), self.__scaleY * self.slider.var.get())
        self.dw.scale('all', 0, 0, ratio, ratio)
        if docenter:
            self.centerPoint()
            self.labelPoint()
        # запоминаем текущий (после изменения) масштаб
        self.__temp['scale'] = self.slider.var.get()

    #-----------------------------------------------------

    def toPoints(self, coords=[], doscale=0):
        """Возвращает список `пойнтов` [pt1, pt2,...] из координат. Обратная к fromPoints.
        COORDS (оп.) список координат [[x,y],[x1,y1]...] в градусах.
        DOSCALE (оп.) пересчитать с учетом масштаба {1|0 (по умолчанию)}."""
        points = []
        # переводим координаты согласно проекции
        for x, y in coords:
            if self.__project in (0, 1):
                _x = float(x) * self.__delta + self.__halfX
                _y = -[self.toMercator(float(y)), float(y)][self.__project] * self.__delta + self.__scaleY / 2.0
            elif self.__project == 2:
                tmp =self.toSphere([x, y])
                if not tmp:
                    continue
                _x, _y = tmp
            if doscale:
                points += map(lambda x: x * self.slider.var.get(), [_x, _y])
            else:
                points += [_x, _y]
        return points

    def fromPoints(self, points=[]):
        """Возвращает список координат [[x,y],[x1,y1]...] в градусах из `пойнтов`. Обратная к toPoints.
        POINTS (оп.) список координат [pt1, pt2,...] в `пойнтах`."""
        b, coords = 0, []
        # переводим x и y (согласно проекции) в `пойнты`
        for point in points:
            if not b:
                x = (point / self.slider.var.get() - self.__halfX) / self.__delta
            elif self.__project == 0:
                y = self.fromMercator(-(point / self.slider.var.get() - self.__scaleY / 2.0) / self.__delta)
                coords += [[x, y]]
            elif self.__project in (1, 2):
                y = -(point / self.slider.var.get() - self.__scaleY / 2.0) / self.__delta
                coords += [[x, y]]
            b = not b
        return coords

    def toCoords(self, strcoords):
        """Возвращает список координат [[x,y],[x1,y1]...] в градусах из строки.
        STRCOORDS строка координат '(x,y),(x1,y1),...'."""
        regstr = '(-?\d+\.?\d*)[ \t]*,[ \t]*(-?\d+\.?\d*)'
        coords = []
        if strcoords:
            coords = [[float(x), float(y)] for x, y in re.findall(regstr, strcoords)]
        return coords

    #-----------------------------------------------------

    """Возвращает значение в диапазоне -y..y.
    X значение в радианах.
    Y диапазон в радианах."""
    global P
    P = lambda x, y: x - 2 * y * int(x / (2 * y) + [-1, 1][x > 0] / 2.0)

    def toMercator(self, y, ylimit=None):
        """Возвращает широту в проекции `Меркатор`. Обратная к fromMercator.
        Y широта в градусах.
        YLIMIT (оп.) ограничение по широте в градусах (по умолчанию __ylimit)."""
        if not ylimit:
            ylimit = self.__ylimit
        if abs(y) > ylimit:
            return self.toMercator([-1, 1][y > 0] * ylimit)
        else:
            return math.degrees(math.log(math.tan(math.radians(y) / 2.0 + math.atan(1))))

    def fromMercator(self, y):
        """Возвращает широту из проекции `Меркатор`. Обратная к toMercator.
        Y широта в градусах в проекции `Меркатор`."""
        return math.degrees(2.0 * (math.atan(math.e**(math.radians(y))) - math.atan(1)))

    def toSphere(self, coords):
        """Возвращает координаты в проекции `Сфера на плоскости`.
        COORDS координаты [x,y] в градусах."""
        # центр сферы
        centerof = self.__temp['centerof']
        x, y, center_x, center_y = [math.radians(float(x)) for x in coords + centerof[0]]
        center_x += math.pi
        if (math.sin(y) * math.sin(center_y) - math.cos(y) * math.cos(center_y) * math.cos(center_x - x)) > 0:
            return [math.degrees(math.cos(y) * math.sin(center_x - x)) * self.__delta + self.__halfX,
                    math.degrees(-math.sin(y) * math.cos(center_y) - math.sin(center_y) * math.cos(y) * math.cos(center_x - x)) * self.__delta + self.__halfY]     

    #-----------------------------------------------------

    def distance(self, coords):
        """Возвращает длину дуги в милях между двумя точками.
        COORDS координаты двух точек [[x,y],[x1,y1]] в градусах."""
        x, y, x1, y1 = [math.radians(x) for x in coords[0] + coords[1]]
        return 3443.918 * math.acos(math.cos(y) * math.cos(y1) * math.cos(x - x1) + math.sin(y) * math.sin(y1))

    def approxPoints(self, coords, scalestep=500):
        """Возвращает список координат как результат аппроксимации двух точек [[x,y],[x1,y1]].
        COORDS координаты двух точек [[x,y],[x1,y1]] в градусах.
        SCALESTEP шаг для аппроксимации в милях."""
        x, y, x1, y1 = coords[0] + coords[1]
        scalestep = int(self.distance(coords) / scalestep)
        if not scalestep:
            return coords
        scale_dx = (x1 - x) / scalestep
        scale_dy = (y1 - y) / scalestep
        _x, _y = x, y
        approx_pts = [[_x, _y]]
        for i in range(scalestep):
            _x += scale_dx
            _y += scale_dy
            approx_pts += [[_x, _y]]
        return approx_pts

def setLanguage(lang=''):
    """Устанавливает язык интерфейса.
    LANG язык {'en'}."""
    global _
    _ = lambda msg : msg
    try:
        if not lang:
            import locale
            lang = locale.getdefaultlocale()[0].split('_')[0]          
        if (lang != 'ru'):
            import gettext
            g = gettext.translation('dbcarta', os.path.join(sys.path[0], 'i18n', 'locale'), (lang,))
            _ = g.gettext
    except:
        return
    return 1

def startCarta():
    """Функция для создания карты.
    Возвращает список с указателями на экземпляр класса и окна карты
    для управления и обработки событий."""
    master = Tk()
    master.columnconfigure(0, weight=1)
    master.rowconfigure(0, weight=1)
    _dbcarta = dbCarta(master)
    # возвращаем инстансы для управления картой из вызывающего
    return (_dbcarta, master)

if __name__ == '__main__':
    _dbcarta, master = startCarta()
    """
    _dbcarta.loadCarta([('DotPort', "Hello test", "((20,10)", u"Hello тест", "(20,10)")], docenter=1)
    _dbcarta.loadCarta([('DotPort', "test1", "((2,19)", u"тест1", "(2,19)")], docenter=1)
    _dbcarta.loadCarta([('Area', "test2", "((-20,15),(-20,0),(-45,10),(-45,15))", u"ареа", "(-20,15)")])
    _dbcarta.loadCarta([('Area', "test3", "((160.88,15),(230,0),(300,10),(207.78,18.9))", u"ареа200", "(160.88,15)")], 1)
    _dbcarta.loadCarta( [('Route', '76.139','(7.2,53.35),(8.2,53.35)', '139', '(7.2,53.35)', )], 1 )
    """
    master.mainloop()
    master.destroy()
